package latticensprings;

import java.util.ArrayList;
import java.util.Iterator;

import codeanticode.glgraphics.GLConstants;

import processing.core.*;
import controlP5.*;
import toxi.geom.*;
import toxi.math.*;
import toxi.geom.mesh2d.*;
import toxi.util.datatypes.*;
import toxi.util.events.*;
import toxi.geom.mesh.subdiv.*;
import toxi.geom.mesh.*;
import toxi.math.waves.*;
import toxi.util.*;
import toxi.volume.*;
import toxi.math.noise.*;
import toxi.physics.*;
import toxi.physics.behaviors.AttractionBehavior;
import peasy.*;


public class LatticeNSprings extends PApplet {
        
        ControlP5 ctrl;
        PeasyCam cam;
        
        ArrayList<Vec3D> points;
        VerletPhysics physics;
        
        CreatureManager cm;
        
        float dragValue;
        float thetaValue;
        float phiValue;
        float radius;
        int pointCount;

        boolean autoTheta, autoPhi, showPhysics, showIso, saveFlag;

        int cnt;
        PFont font;
        
        int DIM = 600;
        int GRID = 25;
        float VS = 2 * DIM/GRID;
        Vec3D SCALE = new Vec3D(DIM,DIM,DIM).scale(2);
        float isoThreshold;
        
        VolumetricSpaceArray volume;
        IsoSurface surface;
        
        TriangleMesh mesh=new TriangleMesh("fluid");

        Vec3D colAmp=new Vec3D(100, 100, 100);
        
        
        public void setup() {
            //size(800,600,OPENGL);
        	size(800,600,GLConstants.GLGRAPHICS);
            //frameRate(25);
            cam = new PeasyCam(this, 600); 
            font = loadFont("Helvetica-16.vlw");
            textFont(font);
            initGui();
              
            cnt = 0;
            points = new ArrayList<Vec3D>();
              
            autoTheta = false;
            autoPhi = false;
           
            initCreatures();

            initPhysics();
            initVolume();
            
            //frame.setLocation(2560, 0);
        }

        public void draw() {
            frame.setTitle("Fps: "+(int)frameRate);
            background(0);
            cam.beginHUD();
            fill(255);
            text("Phi: "+phiValue, 5,16);
            text("Theta: "+thetaValue, 5, 37);
            cam.endHUD();
            
            physics.setDrag(dragValue);
            physics.update();
            cm.update();
            
            if(showPhysics)
            {
                    cm.renderPhysics();
            }
            if(showIso)
            {
                    
                    computeVolume();
                    defineLights();

                    noStroke();
                    beginShape(TRIANGLES);
                    drawFilledMesh();
                    endShape();
            }
            drawBounds();

            
            if(saveFlag){
                    saveFrame("data/images/image####.jpg");
            }
        }
        void defineLights() {
                  // Orange point light on the right
                  pointLight(255, 100, 0,   // Color
                             200, -150, 0); // Position

                  // Blue directional light from the left
                  directionalLight(230, 102, 255, // Color
                                   1, 0, 0);    // The x-, y-, z-axis direction

                  // Yellow spotlight from the front
                  spotLight(255, 255, 255,  // Color
                            0, 40, 200,     // Position
                            0, -0.5f, -0.5f,  // Direction
                            PI / 2, 2);     // Angle, concentration
                  pointLight(255,25,120,
                		  200, 150, 0);
                  
                }
        void initVolume()
        {
                volume = new VolumetricSpaceArray(SCALE, GRID, GRID, GRID);
                surface = new ArrayIsoSurface(volume); 
        }
        
        void initGui()
        {
           thetaValue = 137.5f;
           phiValue = 147.5f;
           radius = 60.0f;
           pointCount = 144;
           dragValue = 0.0f;
           isoThreshold = 100.0f;
           showPhysics = false;
           showIso = true;
           saveFlag = false;
           
          ctrl = new ControlP5(this);
          ControlWindow cw = ctrl.addControlWindow("Angle Control", 300, 300);
          cw.setPositionOfTabs(400, 400);
          Slider thetaSlider = ctrl.addSlider("thetaValue", 0.0f, 180.0f, 137.5f, 5, 5, 100, 10);
          Slider phiSlider = ctrl.addSlider("phiValue", 0.0f, 360.0f, 137.5f, 5, 20, 100, 10);
          Slider radiusSlider = ctrl.addSlider("radius", 1.0f, 300.0f, 60.0f, 5, 35, 100, 10);
          Slider countSlider = ctrl.addSlider("pointCount", 1, 1001, 144, 5, 50, 100, 10);
          Slider dragSlider = ctrl.addSlider("dragValue", 0.0f, 1.0f, 0.0f, 5, 65, 100, 10); 
          Slider isoSlider = ctrl.addSlider("isoThreshold", 0.0f,300.0f, 100.0f, 5, 80, 100, 10);
          
          Toggle sPhy = ctrl.addToggle("showPhysics", 5, 240, 10, 10);
          Toggle sIso = ctrl.addToggle("showIso", 5, 270, 10, 10);
          
          sPhy.moveTo(cw);
          sIso.moveTo(cw);
          
          countSlider.moveTo(cw);
          radiusSlider.moveTo(cw);
          phiSlider.moveTo(cw);
          thetaSlider.moveTo(cw);
          dragSlider.moveTo(cw);
          isoSlider.moveTo(cw);
        }
        
        void drawBounds()
        {
          strokeWeight(1);
          stroke(255,80);
          noFill();
          box(DIM*2);  
        }

        void initCreatures()
        {
                cm = new CreatureManager(this);
                for(int i=0; i<14; i++)
                {
                        cm.spawn(new Vec3D(random(0,300),  random(0,300), random(0,300)));
                }
 
        }
        
        void initPhysics()
        {
                physics = new VerletPhysics();
                physics.setWorldBounds(new AABB(new Vec3D(), new Vec3D(DIM, DIM, DIM)));
                
                for(Creature c: cm.getCreatures())
                {
                        ArrayList<VerletParticle> cells = c.getCells();
                        ArrayList<VerletSpring> bonds = c.getBonds();
                        
                        Iterator<VerletParticle> pIterator = cells.iterator();
                        while(pIterator.hasNext())
                        {
                                VerletParticle p = pIterator.next();
                                physics.addParticle(p);
                                AttractionBehavior repel = new AttractionBehavior(p, 10.0f,1.0f);
                                physics.addBehavior(repel);
                        }
                        Iterator<VerletSpring> sIterator =  bonds.iterator();
                        while(sIterator.hasNext())
                        {
                                VerletSpring s = sIterator.next();
                                physics.addSpring(s);
                        }
                }
        }
        

        void computeVolume() {
                  float cellSize=(float)DIM*2/GRID;
                  Vec3D pos=new Vec3D();
                  Vec3D offset=physics.getWorldBounds().getMin();
                  //Vec3D offset=new Vec3D();

                  float[] volumeData=volume.getData();
                  for(int z=0,index=0; z<GRID; z++) {
                    pos.z=z*cellSize+offset.z;
                    for(int y=0; y<GRID; y++) {
                      pos.y=y*cellSize+offset.y;
                      for(int x=0; x<GRID; x++) {
                        pos.x=x*cellSize+offset.x;
                        float val=0;
                        for(int i=0; i<physics.particles.size(); i++) {
                          Vec3D p=(Vec3D)physics.particles.get(i);
                          float mag=pos.distanceToSquared(p)+0.00001f;
                          val+=1/mag;
                        }
                        volumeData[index++]=val;
                      }
                    }
                  }
                  if (true) {
                    volume.closeSides();
                  }
                  surface.reset();
                  surface.computeSurfaceMesh(mesh,isoThreshold*0.001f);
                }

        void drawFilledMesh() {
                  int num=mesh.getNumFaces();
                  mesh.computeVertexNormals();
                  for(int i=0; i<num; i++) {
                    Face f=mesh.faces.get(i);
                    Vec3D col=f.a.add(colAmp).scaleSelf(0.5f);
                    fill(col.x,col.y,col.z);
                    normal(f.a.normal);
                    vertex(f.a);
                    col=f.b.add(colAmp).scaleSelf(0.5f);
                    fill(col.x,col.y,col.z);
                    normal(f.b.normal);
                    vertex(f.b);
                    col=f.c.add(colAmp).scaleSelf(0.5f);
                    fill(col.x,col.y,col.z);
                    normal(f.c.normal);
                    vertex(f.c);
                  }
                }


        void normal(Vec3D v) {
                normal(v.x,v.y,v.z);
        }

        void vertex(Vec3D v) {
                vertex(v.x,v.y,v.z);
        }
        
        public void keyPressed()
        {
          switch(key)
          {

          case('p'):
                println("p is pressed!");
          break;
          case(' '):
                  saveFlag = !saveFlag;
          break;
            case('s'):
             thetaValue += 0.1f;
             if(thetaValue > 180.0f)
               {thetaValue = 180.0f;}
             break;
            case('x'):
              thetaValue -= 0.1f;
              if(thetaValue < 0.0f)
                {thetaValue = 0.0f;}
              break;
             case('a'):
               phiValue += 0.1f;
               if(phiValue > 360.0f)
                { phiValue = 360;}
               break;
             case('z'):
               phiValue -= 0.1f;
               if(phiValue < 0.0f)
                 {phiValue = 0.0f;}
               break;
             case('w'):
               autoTheta = !autoTheta;
               break;
             case('q'):
               autoPhi = !autoPhi;
               break;
          }
          
        }
        
        public static void main(String _args[]) {
                PApplet.main(new String[] { latticensprings.LatticeNSprings.class.getName() });
        }
}
